/**
 * MIT License
 * 
 * Copyright (c) 2024 - present @ ebraid
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <kernel/kobject.h>
#include <kernel/syslog.h>
#include <lib/eb_string.h>
#include <mm/mem.h>


/**
 * The root object is the system's root directory, and all objects should be located below it. 
 * Because it is a directory, its kops are set to NULL.
 */
static struct kobj kobj_root = {
    .name = "root",
    .kref = 1,
    .kops = NULL,
    .parent = &kobj_root,
};


kobj_t* kobj_get_root(void)
{
    return &kobj_root;
}


bool kobj_is_root(kobj_t *kobj)
{
    return kobj == &kobj_root;
}


bool kobj_is_dir(kobj_t *kobj)
{
    return kobj->kops == NULL;
}


bool kobj_is_attr(kobj_t *kobj)
{
    return kobj->kops != NULL;
}


bool kobj_is_child(kobj_t *parent, kobj_t *kobj)
{
    if(parent == NULL) {
        SYSLOG_WARN("the parent is NULL, it will be set as root");
        parent = &kobj_root;
    }

    kobj_t *kobj_node;
    struct list_node *head = &parent->child;

    list_for_each_entry(kobj_node, head, kobj_t, entry) {
        if(kobj_node == kobj) {
            return true;
        }
    }

    SYSLOG_WARN("can not find kobj in the parent");
    return false;
}


static void kobj_release(struct kobj *kobj)
{
    SYSLOG_INFO("release kobj");

    if(kobj != &kobj_root) {
        kfree(kobj);
    }
}


static void kobj_kref_get(struct kobj *kobj)
{
    kobj->kref ++;
}


static void kobj_kref_put(struct kobj *kobj)
{
    if(kobj->kref) {
        kobj->kref --;
    }
    else {
        kobj_release(kobj);
    }
}


void kobj_root_init(void)
{
    kobj_root.parent = &kobj_root;
    list_init(&kobj_root.entry);
    list_init(&kobj_root.child);
}


void kobj_init(kobj_t *kobj, const char *name, struct kobj_ops *kops)
{
    kobj->name = name;
    kobj->kops = kops;
    kobj->parent = NULL;
    kobj->kref = 1;

    list_init(&kobj->child);
}


void kobj_set_parent(kobj_t *kobj, kobj_t* parent)
{
    kobj->parent = parent;
}


kobj_t* kobj_alloc(const char *name, struct kobj_ops *kops)
{
    kobj_t* kobj = kmalloc(sizeof(kobj_t));
    if(kobj == NULL) {
        SYSLOG_ERROR("alloc kobj_t failed");
        return NULL;
    }

    kobj_init(kobj, name, kops);
    
    return kobj;
}


kobj_t* kobj_alloc_dir(const char *name)
{
    return kobj_alloc(name, NULL);
}


kobj_t* kobj_alloc_attr(const char *name, struct kobj_ops *ops)
{
    return kobj_alloc(name, ops);
}


bool kobj_add(kobj_t *parent, kobj_t *kobj)
{
    if(parent == NULL) {
        parent = &kobj_root;
    }

    /* the kobj is attr */
    if(kobj_is_attr(parent)) {
        SYSLOG_ERROR("kobj is attr, you should not add kobj into a attr");
        return false;
    }

    /* can not find kobject, so it can be added into parent */
    if(kobj_find_by_name(parent, kobj->name)) {
        SYSLOG_ERROR("parent has the same name kobj, %s kobject can not add", kobj->name);
        return false;
    }

    list_add_node_at_tail(&parent->child, &kobj->entry);
    kobj->parent = parent;
    kobj_kref_get(parent);

    return true;
}


bool kobj_remove(kobj_t *parent, kobj_t *kobj)
{
    kobj_t *kobj_node;
    struct list_node *head = &parent->child;

    if(parent == NULL) {
        parent = &kobj_root;
    }

    list_for_each_entry(kobj_node, head, kobj_t, entry) {
        if(kobj_node == kobj) {
            list_del_node(&kobj_node->entry);
            kobj_kref_put(parent);
            return true;
        }
    }
    
    SYSLOG_ERROR("can not find kobj in the parent");
    return false;
}


kobj_t* kobj_find_by_name(kobj_t *parent, const char *name)
{
    if(parent == NULL) {
        parent = &kobj_root;
    }

    kobj_t *kobj_node;
    struct list_node *head = &parent->child;

    list_for_each_entry(kobj_node, head, kobj_t, entry) {
        if(eb_strcmp(kobj_node->name, name) == 0) {
            return kobj_node;
        }
    }

    return NULL;
}


kobj_t* kobj_create_dir(kobj_t *parent, const char *name)
{
    kobj_t *kobj = kobj_alloc_dir(name);
    if(kobj == NULL) {
        return NULL;
    }

    if(!kobj_add(parent, kobj)) {
        kfree(kobj);
        return NULL;
    }
    return kobj;
}


kobj_t* kobj_create_attr(kobj_t *parent, const char *name, struct kobj_ops *kops)
{
    kobj_t *kobj = kobj_alloc_attr(name, kops);
    if(kobj == NULL) {
        SYSLOG_ERROR("alloc kobj failed");
        return NULL;
    }

    if(!kobj_add(parent, kobj)) {
        kfree(kobj);
        return NULL;
    }
    return kobj;
}


bool kobj_remove_self(kobj_t *kobj)
{
    if(kobj == NULL) {
        return false;
    }

    kobj_t *kobj_node;
    struct list_node *head = &kobj->child;

    if(kobj_is_root(kobj)) {
        return false;
    }

    if(kobj_is_dir(kobj)) {
        list_for_each_entry(kobj_node, head, kobj_t, entry) {
            kobj_remove_self(kobj_node);
        }
    }

    kobj_t *parent = kobj->parent;

    if(parent != kobj) {
        bool ret = kobj_remove(parent, kobj);
        if(ret) {
            kfree(kobj);
            return true;
        }
    }

    kfree(kobj);
    return true;
}
